Use `omniauth-dropbox` instead of requiring an api key per agent; And `dropbox-api` instead of rolling our own.

Guilherme J. Tramontina 9 years ago
parent
commit
04a244661d

+ 5 - 2
.env.example

@@ -53,8 +53,8 @@ INVITATION_CODE=try-huginn
53 53
 
54 54
 # Outgoing email settings.  To use Gmail or Google Apps, put your Google Apps domain or gmail.com
55 55
 # as the SMTP_DOMAIN and your Gmail username and password as the SMTP_USER_NAME and SMTP_PASSWORD.
56
-# 
57
-# PLEASE NOTE: In order to enable emails locally (e.g., when not in the production Rails environment), 
56
+#
57
+# PLEASE NOTE: In order to enable emails locally (e.g., when not in the production Rails environment),
58 58
 # you must also change config.action_mailer.perform_deliveries in config/environments/development.rb.
59 59
 
60 60
 SMTP_DOMAIN=your-domain-here.com
@@ -92,6 +92,9 @@ GITHUB_OAUTH_SECRET=
92 92
 TUMBLR_OAUTH_KEY=
93 93
 TUMBLR_OAUTH_SECRET=
94 94
 
95
+DROPBOX_OAUTH_KEY=
96
+DROPBOX_OAUTH_SECRET=
97
+
95 98
 #############################
96 99
 #  AWS and Mechanical Turk  #
97 100
 #############################

+ 4 - 0
Gemfile

@@ -26,6 +26,10 @@ gem 'omniauth-twitter'
26 26
 gem 'tumblr_client'
27 27
 gem 'omniauth-tumblr'
28 28
 
29
+# Dropbox Agents
30
+gem 'dropbox-api'
31
+gem 'omniauth-dropbox'
32
+
29 33
 # Optional Services.
30 34
 gem 'omniauth-37signals'          # BasecampAgent
31 35
 # gem 'omniauth-github'

+ 8 - 0
Gemfile.lock

@@ -95,6 +95,10 @@ GEM
95 95
     dotenv-deployment (0.0.2)
96 96
     dotenv-rails (0.11.1)
97 97
       dotenv (= 0.11.1)
98
+    dropbox-api (0.4.2)
99
+      hashie
100
+      multi_json
101
+      oauth
98 102
     em-http-request (1.1.2)
99 103
       addressable (>= 2.3.4)
100 104
       cookiejar
@@ -206,6 +210,8 @@ GEM
206 210
     omniauth-37signals (1.0.5)
207 211
       omniauth (~> 1.0)
208 212
       omniauth-oauth2 (~> 1.0)
213
+    omniauth-dropbox (0.2.0)
214
+      omniauth-oauth (~> 1.0)
209 215
     omniauth-oauth (1.0.1)
210 216
       oauth
211 217
       omniauth (~> 1.0)
@@ -417,6 +423,7 @@ DEPENDENCIES
417 423
   devise (~> 3.2.4)
418 424
   dotenv-deployment
419 425
   dotenv-rails
426
+  dropbox-api
420 427
   em-http-request (~> 1.1.2)
421 428
   faraday (~> 0.9.0)
422 429
   faraday_middleware
@@ -443,6 +450,7 @@ DEPENDENCIES
443 450
   nokogiri (~> 1.6.1)
444 451
   omniauth
445 452
   omniauth-37signals
453
+  omniauth-dropbox
446 454
   omniauth-tumblr
447 455
   omniauth-twitter
448 456
   pg

+ 5 - 0
app/assets/stylesheets/application.css.scss.erb

@@ -273,4 +273,9 @@ h2 .scenario, a span.label.scenario {
273 273
     color: #fff;
274 274
     background-color: #444;
275 275
   }
276
+
277
+  &.btn-auth-dropbox {
278
+    color: #fff;
279
+    background-color: #007EE5;
280
+  }
276 281
 }

+ 35 - 0
app/concerns/dropbox_concern.rb

@@ -0,0 +1,35 @@
1
+module DropboxConcern
2
+  extend ActiveSupport::Concern
3
+
4
+  included do
5
+    include Oauthable
6
+    valid_oauth_providers :dropbox
7
+    gem_dependency_check { defined?(Dropbox) && Devise.omniauth_providers.include?(:dropbox) }
8
+  end
9
+
10
+  def dropbox
11
+    Dropbox::API::Config.app_key = consumer_key
12
+    Dropbox::API::Config.app_secret = consumer_secret
13
+    Dropbox::API::Config.mode = 'dropbox'
14
+    Dropbox::API::Client.new(token: oauth_token, secret: oauth_token_secret)
15
+  end
16
+
17
+  private
18
+
19
+  def consumer_key
20
+    (config = Devise.omniauth_configs[:dropbox]) && config.strategy.consumer_key
21
+  end
22
+
23
+  def consumer_secret
24
+    (config = Devise.omniauth_configs[:dropbox]) && config.strategy.consumer_secret
25
+  end
26
+
27
+  def oauth_token
28
+    service && service.token
29
+  end
30
+
31
+  def oauth_token_secret
32
+    service && service.secret
33
+  end
34
+
35
+end

+ 1 - 1
app/helpers/application_helper.rb

@@ -43,7 +43,7 @@ module ApplicationHelper
43 43
 
44 44
   def icon_for_service(service)
45 45
     case service.to_sym
46
-    when :twitter, :tumblr, :github
46
+    when :twitter, :tumblr, :github, :dropbox
47 47
       "<i class='fa fa-#{service}'></i>".html_safe
48 48
     else
49 49
       "<i class='fa fa-lock'></i>".html_safe

+ 12 - 29
app/models/agents/dropbox_watch_agent.rb

@@ -1,11 +1,13 @@
1 1
 module Agents
2 2
   class DropboxWatchAgent < Agent
3
+    include DropboxConcern
4
+
3 5
     cannot_receive_events!
4 6
     default_schedule "every_1m"
5 7
 
6 8
     description <<-MD
9
+      #{'## Include the `dropbox-api` and `omniauth-dropbox` gems in your `Gemfile` and set `DROPBOX_OAUTH_KEY` and `DROPBOX_OAUTH_SECRET` in your environment to use Dropbox Agents.' if dependencies_missing?}
7 10
       The _DropboxWatchAgent_ watches the given `dir_to_watch` and emits events with the detected changes.
8
-      It requires a [Dropbox App](https://www.dropbox.com/developers/apps) and its `access_token`, which will be used to authenticate on your account.
9 11
     MD
10 12
 
11 13
     event_description <<-MD
@@ -24,14 +26,12 @@ module Agents
24 26
 
25 27
     def default_options
26 28
       {
27
-        'access_token' => 'your_dropbox_app_access_token',
28 29
         'dir_to_watch' => '/',
29 30
         'expected_update_period_in_days' => 1
30 31
       }
31 32
     end
32 33
 
33 34
     def validate_options
34
-      errors.add(:base, 'The `access_token` property is required.') unless options['access_token'].present?
35 35
       errors.add(:base, 'The `dir_to_watch` property is required.') unless options['dir_to_watch'].present?
36 36
       errors.add(:base, 'Invalid `expected_update_period_in_days` format.') unless options['expected_update_period_in_days'].present? && is_positive_integer?(options['expected_update_period_in_days'])
37 37
     end
@@ -41,8 +41,7 @@ module Agents
41 41
     end
42 42
 
43 43
     def check
44
-      api = DropboxAPI.new(interpolated['access_token'])
45
-      current_contents = api.dir(interpolated['dir_to_watch'])
44
+      current_contents = ls(interpolated['dir_to_watch'])
46 45
       diff = DropboxDirDiff.new(previous_contents, current_contents)
47 46
       create_event(payload: diff.to_hash) unless previous_contents.nil? || diff.empty?
48 47
 
@@ -57,6 +56,14 @@ module Agents
57 56
       false
58 57
     end
59 58
 
59
+    def ls(dir_to_watch)
60
+      dropbox.ls(dir_to_watch).map { |entry| slice_json(entry, 'path', 'rev', 'modified') }
61
+    end
62
+
63
+    def slice_json(json, *keys)
64
+      keys.each_with_object({}){|key, hash| hash[key.to_s] = json[key.to_s]}
65
+    end
66
+
60 67
     def previous_contents
61 68
       self.memory['contents']
62 69
     end
@@ -67,30 +74,6 @@ module Agents
67 74
 
68 75
     # == Auxiliary classes ==
69 76
 
70
-    class DropboxAPI
71
-      class ResourceNotFound < RuntimeError; end
72
-
73
-      include HTTParty
74
-      base_uri 'https://api.dropbox.com/1'
75
-
76
-      def initialize(access_token)
77
-        @options = { query: { access_token: access_token } }
78
-      end
79
-
80
-      def dir(to_watch)
81
-        options = @options.deep_merge({ query: { list: true } })
82
-        response = self.class.get("/metadata/auto#{to_watch}", options)
83
-        raise ResourceNotFound.new(to_watch) if response.not_found?
84
-        JSON.parse(response)['contents'].map { |entry| slice_json(entry, 'path', 'rev', 'modified') }
85
-      end
86
-
87
-      private
88
-
89
-      def slice_json(json, *keys)
90
-        keys.each_with_object({}){|key, hash| hash[key.to_s] = json[key.to_s]}
91
-      end
92
-    end
93
-
94 77
     class DropboxDirDiff
95 78
       def initialize(previous, current)
96 79
         @previous, @current = [previous || [], current || []]

+ 1 - 3
app/models/service.rb

@@ -59,12 +59,10 @@ class Service < ActiveRecord::Base
59 59
 
60 60
   def self.provider_specific_options(omniauth)
61 61
     case omniauth['provider'].to_sym
62
-      when :twitter, :github
63
-        { name: omniauth['info']['nickname'] }
64 62
       when :'37signals'
65 63
         { user_id: omniauth['extra']['accounts'][0]['id'], name: omniauth['info']['name'] }
66 64
       else
67
-        { name: omniauth['info']['nickname'] }
65
+        { name: omniauth['info']['nickname'] || omniauth['info']['name'] }
68 66
     end
69 67
   end
70 68
 

+ 8 - 1
config/initializers/devise.rb

@@ -136,7 +136,7 @@ Devise.setup do |config|
136 136
   # The time you want to timeout the user session without activity. After this
137 137
   # time the user will be asked for credentials again. Default is 30 minutes.
138 138
   # config.timeout_in = 30.minutes
139
-  
139
+
140 140
   # If true, expires auth token on session timeout.
141 141
   # config.expire_auth_token_on_timeout = false
142 142
 
@@ -213,6 +213,7 @@ Devise.setup do |config|
213 213
   # Add a new OmniAuth provider. Check the wiki for more information on setting
214 214
   # up on your models and hooks.
215 215
   # config.omniauth :github, 'APP_ID', 'APP_SECRET', :scope => 'user,public_repo'
216
+
216 217
   if defined?(OmniAuth::Strategies::Twitter) &&
217 218
      (key = ENV["TWITTER_OAUTH_KEY"]).present? &&
218 219
      (secret = ENV["TWITTER_OAUTH_SECRET"]).present?
@@ -237,6 +238,12 @@ Devise.setup do |config|
237 238
     config.omniauth :github, key, secret
238 239
   end
239 240
 
241
+  if defined?(OmniAuth::Strategies::Dropbox) &&
242
+     (key = ENV["DROPBOX_OAUTH_KEY"]).present? &&
243
+     (secret = ENV["DROPBOX_OAUTH_SECRET"]).present?
244
+    config.omniauth :dropbox, key, secret
245
+  end
246
+
240 247
   # ==> Warden configuration
241 248
   # If you want to use other strategies, that are not supported by Devise, or
242 249
   # change the failure app, you can configure them inside the config.warden block.

+ 1 - 0
config/locales/devise.en.yml

@@ -54,6 +54,7 @@ en:
54 54
       tumblr: 'Tumblr'
55 55
       github: 'GitHub'
56 56
       37signals: '37Signals (Basecamp)'
57
+      dropbox: 'Dropbox'
57 58
     mailer:
58 59
       confirmation_instructions:
59 60
         subject: 'Confirmation instructions'

+ 7 - 75
spec/models/agents/dropbox_watch_agent_spec.rb

@@ -11,6 +11,7 @@ describe Agents::DropboxWatchAgent do
11 11
       }
12 12
     )
13 13
     @agent.user = users(:bob)
14
+    @agent.service = services(:generic)
14 15
     @agent.save!
15 16
   end
16 17
 
@@ -29,11 +30,6 @@ describe Agents::DropboxWatchAgent do
29 30
   describe '#valid?' do
30 31
     before(:each) { expect(@agent.valid?).to eq true }
31 32
 
32
-    it 'requires the "access_token"' do
33
-      @agent.options[:access_token] = nil
34
-      expect(@agent.valid?).to eq false
35
-    end
36
-
37 33
     it 'requires a "dir_to_watch"' do
38 34
       @agent.options[:dir_to_watch] = nil
39 35
       expect(@agent.valid?).to eq false
@@ -54,11 +50,11 @@ describe Agents::DropboxWatchAgent do
54 50
 
55 51
   describe '#check' do
56 52
 
57
-    let(:first_result) { 'first_result' }
53
+    let(:first_result) { [{ 'path' => '1.json', 'rev' => '1', 'modified' => '01-01-01' }] }
58 54
 
59 55
     before(:each) do
60
-      stub.proxy(Agents::DropboxWatchAgent::DropboxAPI).new('70k3n') do |api|
61
-        stub(api).dir('/my/dropbox/dir') { first_result }
56
+      stub.proxy(Dropbox::API::Client).new do |api|
57
+        stub(api).ls('/my/dropbox/dir') { first_result }
62 58
       end
63 59
     end
64 60
 
@@ -79,13 +75,13 @@ describe Agents::DropboxWatchAgent do
79 75
 
80 76
     context 'subsequent calls' do
81 77
 
82
-      let(:second_result) { 'second_result' }
78
+      let(:second_result) { [{ 'path' => '2.json', 'rev' => '1', 'modified' => '02-02-02' }] }
83 79
 
84 80
       before(:each) do
85 81
         @agent.memory = { 'contents' => 'not_empty' }
86 82
 
87
-        stub.proxy(Agents::DropboxWatchAgent::DropboxAPI).new('70k3n') do |api|
88
-          stub(api).dir('/my/dropbox/dir') { second_result }
83
+        stub.proxy(Dropbox::API::Client).new do |api|
84
+          stub(api).ls('/my/dropbox/dir') { second_result }
89 85
         end
90 86
       end
91 87
 
@@ -174,68 +170,4 @@ describe Agents::DropboxWatchAgent do
174 170
     end
175 171
   end
176 172
 
177
-  describe Agents::DropboxWatchAgent::DropboxAPI do
178
-    let(:dir_to_watch) { '/my/dropbox/dir' }
179
-    let(:access_token) { '70k3n' }
180
-    let(:api_url) { "https://api.dropbox.com/1/metadata/auto#{dir_to_watch}?access_token=#{access_token}&list=true" }
181
-
182
-    describe '#dir' do
183
-
184
-      context 'when the provided path exists' do
185
-        before do
186
-          stub_request(:get, api_url).to_return(body: JSON.dump({
187
-            contents: [
188
-                {
189
-                    bytes: 0,
190
-                    icon: "folder",
191
-                    is_dir: true,
192
-                    modified: "Mon, 11 Mar 2013 15:41:44 +0000",
193
-                    path: "#{dir_to_watch}/1.json",
194
-                    rev: "1",
195
-                    revision: 14743,
196
-                    root: "dropbox",
197
-                    size: "0 bytes",
198
-                    thumb_exists: false
199
-                },
200
-                {
201
-                    bytes: 0,
202
-                    icon: "folder",
203
-                    is_dir: true,
204
-                    modified: "Mon, 12 Mar 2013 15:41:44 +0000",
205
-                    path: "#{dir_to_watch}/2.json",
206
-                    rev: "4",
207
-                    revision: 113022,
208
-                    root: "dropbox",
209
-                    size: "0 bytes",
210
-                    thumb_exists: false
211
-                }
212
-            ],
213
-            some: "other",
214
-            things: "we",
215
-            dont: "need"
216
-          }))
217
-        end
218
-
219
-        it 'trims down the attributes of the response to our needs' do
220
-          dir_list = Agents::DropboxWatchAgent::DropboxAPI.new(access_token).dir(dir_to_watch)
221
-          expect(dir_list).to eq [
222
-            { 'path' => "#{dir_to_watch}/1.json", 'rev' => '1', 'modified' => 'Mon, 11 Mar 2013 15:41:44 +0000' },
223
-            { 'path' => "#{dir_to_watch}/2.json", 'rev' => '4', 'modified' => 'Mon, 12 Mar 2013 15:41:44 +0000' }
224
-          ]
225
-        end
226
-      end
227
-
228
-      context 'when the provided path does not exist' do
229
-        before { stub_request(:get, api_url).to_return(status: 404, body: '{"error": "Not Found"}') }
230
-
231
-        it 'raises a ResourceNotFound error' do
232
-          expect {
233
-            Agents::DropboxWatchAgent::DropboxAPI.new(access_token).dir(dir_to_watch)
234
-          }.to raise_error(Agents::DropboxWatchAgent::DropboxAPI::ResourceNotFound, dir_to_watch)
235
-        end
236
-      end
237
-
238
-    end
239
-
240
-  end
241 173
 end